﻿#region License Info

//Component of Cronos Package, http://www.codeplex.com/cronos
//Copyright (C) 2009 Anthony Brockwell

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#endregion

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using PdfSharp.Drawing;

namespace ExtraControlLibrary
{
    public partial class PartitionedBlankGraph : UserControl
    {
        private string title = "Default Title";

        protected PartitionedBlankGraph()
        {
            TitleVerticalProportion = 0.12;
            GraphVerticalProportion = 0.78;
            TitleFontSize = 12;
            LabelFontSize = 8;

            InitializeComponent();

            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.DoubleBuffer, true);
        }

        public Rectangle GraphRectangle { get; protected set; }
        public Rectangle TitleRectangle { get; protected set; }
        public Rectangle XLabelRectangle { get; protected set; }
        public Rectangle YLabelRectangle { get; protected set; }

        public int TitleFontSize { get; set; }
        public int LabelFontSize { get; set; }


        /// <summary>
        /// The title to be displayed above the plot.
        /// </summary>
        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                Refresh();
            }
        }

        public double TitleVerticalProportion { get; set; }

        public double GraphVerticalProportion { get; set; }

        protected void ClearGraphArea(Graphics g)
        {
            // Then put down the background white rectangle.
            g.FillRectangle(Brushes.White, GraphRectangle);
            // and the frame.
            g.DrawRectangle(Pens.Black, GraphRectangle);
        }

        protected void DrawGraphFrame(Object graphics)
        {
            var g = graphics as Graphics;
            var xg = graphics as XGraphics;

            if (g != null)
                g.DrawRectangle(Pens.Black, GraphRectangle);
            if (xg != null)
                xg.DrawRectangle(Pens.Black, GraphRectangle);
        }

        protected void DrawTitle(Font title_font, Object graphics)
        {
            var g = graphics as Graphics;
            var xg = graphics as XGraphics;
            if (g != null)
                using (var sf = new StringFormat())
                {
                    sf.Alignment = StringAlignment.Center;

                    // First the title.
                    Size preferredSize;
                    using (Graphics g2 = CreateGraphics())
                        preferredSize = g2.MeasureString(Title, title_font).ToSize();

                    g.DrawString(Title, title_font, Brushes.Black, TitleRectangle.Left + TitleRectangle.Width/2,
                                 TitleRectangle.Top + TitleRectangle.Height/2 - preferredSize.Height/2,
                                 sf);
                }
            if (xg != null)
            {
                var xsf = new XStringFormat();
                xsf.Alignment = XStringAlignment.Center;

                // First the title.
                Size preferredSize;
                using (Graphics g2 = CreateGraphics())
                    preferredSize = g2.MeasureString(Title, title_font).ToSize();

                xg.DrawString(Title, title_font, Brushes.Black, TitleRectangle.Left + TitleRectangle.Width/2,
                              TitleRectangle.Top + TitleRectangle.Height/2 - preferredSize.Height/2,
                              xsf);
            }
        }


        /// <summary>
        /// This function breaks up the window into sub-rectangles for different Components of the plot.
        /// The sub-rectangles are properties of the PartitionedBlankGraph class.
        /// </summary>
        public void CreatePartition(Rectangle bounds)
        {
            float w = bounds.Width;
            float h = bounds.Height;
            YLabelRectangle = new Rectangle(0, 0, (int) (w*0.10), (int) h - 1);
            TitleRectangle =
                new Rectangle(YLabelRectangle.Width, 0, (int) ((w - 1 - YLabelRectangle.Width)*0.9),
                              (int) (h*TitleVerticalProportion));
            GraphRectangle =
                new Rectangle(YLabelRectangle.Width, TitleRectangle.Bottom, TitleRectangle.Width,
                              (int) (h*GraphVerticalProportion));
            XLabelRectangle =
                new Rectangle(YLabelRectangle.Width, GraphRectangle.Bottom, TitleRectangle.Width,
                              (int) h - GraphRectangle.Bottom - 1);
        }

        protected virtual double MinYValue()
        {
            return 0.0;
        }

        protected virtual double MaxYValue()
        {
            return 1.0;
        }

        protected virtual double LeftXValue()
        {
            return 0.0;
        }

        protected virtual double RightXValue()
        {
            return 1.0;
        }

        /// <summary>
        /// Converts from double-valued y coordinate to pixel y-position within control.
        /// </summary>
        /// <param name="ty"></param>
        /// <returns></returns>
        public int MaptoYPixel(double ty, double y0, double y1)
        {
            var ypix = (int) (-(ty - y0)/(y1 - y0)*GraphRectangle.Height + GraphRectangle.Bottom);
            return ypix;
        }

        public float MaptoYPosition(double ty)
        {
            double y0 = MinYValue();
            double y1 = MaxYValue();
            var ypix = (float) (-(ty - y0)/(y1 - y0)*GraphRectangle.Height + GraphRectangle.Bottom);
            return ypix;
        }

        /// <summary>
        /// Converts from double-valued y coordinate to pixel y-position within control.
        /// </summary>
        /// <param name="ty"></param>
        /// <returns></returns>
        public int MaptoYPixel(double ty)
        {
            double y0 = MinYValue();
            double y1 = MaxYValue();
            var ypix = (int) (-(ty - y0)/(y1 - y0)*GraphRectangle.Height + GraphRectangle.Bottom);
            return ypix;
        }

        public float MaptoXPosition(double tx)
        {
            double x0 = LeftXValue(), x1 = RightXValue();
            var xpix = (float) ((tx - x0)/(x1 - x0)*GraphRectangle.Width + GraphRectangle.Left);
            return xpix;
        }

        /// <summary>
        /// Converts from double-valued x coordinate to pixel x-position within control.
        /// </summary>
        /// <param name="tx"></param>
        /// <returns></returns>
        public int MaptoXPixel(double tx)
        {
            double x0 = LeftXValue(), x1 = RightXValue();
            var xpix = (int) ((tx - x0)/(x1 - x0)*GraphRectangle.Width + GraphRectangle.Left);
            return xpix;
        }

        /// <summary>
        /// Maps a pixel x-position within the control to a double x-value.
        /// </summary>
        /// <param name="pixel_x"></param>
        /// <returns></returns>
        public double XValueToPixel(int pixel_x)
        {
            double frac = ((double) pixel_x - GraphRectangle.Left)/(GraphRectangle.Width);
            return frac*(RightXValue() - LeftXValue()) + LeftXValue();
        }

        /// <summary>
        /// Maps a pixel y-position within the control to a double y-value.
        /// </summary>
        /// <param name="pixel_y"></param>
        /// <returns></returns>
        public double YValueToPixel(int pixel_y)
        {
            double frac = -((double) pixel_y - GraphRectangle.Bottom)/(GraphRectangle.Height);
            return frac*(MaxYValue() - MinYValue()) + MinYValue();
        }


        ///<summary>
        /// This function returns a "nice" subunit of the range (x1,x2) to use in between graph divisions.
        /// By choosing prelim_scale, for instance, to be M_PI, it gives nice divisions regarding the unit as M_PI (useful for plotting spectral densities).
        /// Usually prelim_scale would just be 1.0.  If timemode==true, it will aim for secs/mins/hours/etc, with
        /// time measured in DateTime.Ticks.
        /// </summary>
        /// <param name="x1">start of range</param>
        /// <param name="x2">end of range</param>
        /// <param name="prelim_scale">one "unit", ignored if timemode==true</param>
        /// <param name="timemode">if true, then x1,x2 are regarded as DateTime.Ticks</param>
        protected static double GetNiceUnitForRange(double x1, double x2, double prelim_scale, bool timemode)
        {
            if (x2 < x1)
            {
                double tempx = x2;
                x2 = x1;
                x1 = tempx;
            }
            if (!timemode) // for non-time x-axis, just choose a nice division in base 10
            {
                x1 /= prelim_scale;
                x2 /= prelim_scale;
                if (x2 == x1)
                    return 1.0;
                double w = (x2 - x1), pw = Math.Floor(Math.Log10(w)), tens = Math.Pow(10.0, pw), units;
                w /= tens; // now we have a number from 1.0 to 9.999...
                if (w < 1.4)
                    units = 0.2;
                else if (w < 2.0)
                    units = 0.25;
                else units = w < 4 ? 0.5 : 1.0;
                return (units*tens*prelim_scale);
            }

            double tickrange = x2 - x1;
            double secondticks = new TimeSpan(0, 0, 0, 1).Ticks; // ticks per second
            if (tickrange == 0)
                return 1.0;

            double[] goodones = {
                                    0.001, 0.0025, 0.01, 0.025, 0.1, 0.25, 1.0,
                                    2.0, 5.0, 15.0, 60.0, 120.0, 300.0, 600.0, 1800.0, 3600.0,
                                    7200.0, 14400.0, 3600.0*12, 3600.0*24, 3600.0*48, 3600.0*24*7,
                                    3600.0*24*7*4, 3600.0*24*7*12, 3600.0*24*7*48,
                                    3600.0*24*7*204,
                                    3600.0*24*7*408,
                                    3600.0*24*7*1040,
                                    3600.0*24*7*50*52
                                }; // "good" time units, measured in ticks

            // we want to pick something that yields 4-16 grid divisions
            double bestunit = goodones[0];
            foreach (double suggested in goodones)
            {
                bestunit = suggested*secondticks;
                var gridsize = (int) (Math.Floor(tickrange/bestunit) + 0.5);
                if (4 <= gridsize && gridsize <= 16)
                    return bestunit;
            }

            return bestunit;
        }


        /// <summary>
        /// This function draws the xlabel specified by s in XLabelRectangle.
        /// It returns a bounding rectangle for the label so that the
        /// caller knows how much space has(/would have) been used.
        /// </summary>
        /// <param name="g"></param>
        /// <param name="xpos">x-position</param>
        /// <param name="s">string to be drawn (or just sized if fakedraw is false)</param>
        /// <param name="f">font to be used</param>
        /// <param name="sf">string format</param>
        /// <param name="fakedraw">bool fakedraw should be true if only calling to determine size</param>
        /// <returns></returns>
        private Rectangle DrawXLabel(Graphics g, double xpos, string s, Font f, StringFormat sf, bool fakedraw)
        {
            int xpix = MaptoXPixel(xpos);

            // Get the Size needed to accommodate the formatted Text.
            Size preferredSize;
            using (Graphics g2 = CreateGraphics())
                preferredSize = g2.MeasureString(s, f).ToSize();

            // And put the text down, so that it is centered horizontally at the appropriate x position
            var labelrectangle =
                new Rectangle(xpix - preferredSize.Width/2, XLabelRectangle.Top, preferredSize.Width,
                              preferredSize.Height);
            if (!fakedraw)
            {
                // first text
                g.DrawString(s, f, Brushes.Black, xpix,
                             XLabelRectangle.Top + 4 + preferredSize.Height/2, sf);
                // then the little marker line
                g.DrawLine(Pens.Black, xpix, XLabelRectangle.Top, xpix, XLabelRectangle.Top + 3);
            }
            return labelrectangle;
        }

        /// <summary>
        /// This function is the same as DrawXLabel but draws to an XGraphics pdf rendering object.
        /// </summary>
        private Rectangle XDrawXLabel(XGraphics g, double xpos, string s, Font f, XStringFormat sf, bool fakedraw)
        {
            float xpix = MaptoXPosition(xpos);

            // Get the Size needed to accommodate the formatted Text.
            Size preferredSize;
            using (Graphics g2 = CreateGraphics())
                preferredSize = g2.MeasureString(s, f).ToSize();

            // And put the text down, so that it is centered horizontally at the appropriate x position
            var labelrectangle =
                new Rectangle((int) xpix - preferredSize.Width/2, XLabelRectangle.Top, preferredSize.Width,
                              preferredSize.Height);
            if (!fakedraw)
            {
                // first text
                g.DrawString(s, f, Brushes.Black, xpix,
                             XLabelRectangle.Top + 4 + preferredSize.Height/2, sf);
                // then the little marker line
                g.DrawLine(Pens.Black, xpix, XLabelRectangle.Top, xpix, XLabelRectangle.Top + 3);
            }
            return labelrectangle;
        }


        protected void DrawAllXLabels(Object graphicsObject, Font label_font,
                                      IList<double> xaxisvals, IList<string> xaxisvallabels, string label)
        {
            var g = graphicsObject as Graphics;
            var xg = graphicsObject as XGraphics;
            if (g != null)
            {
                var lastSuccess = new Rectangle(0, 0, 0, 0);
                using (var sf = new StringFormat())
                {
                    sf.Alignment = StringAlignment.Center;
                    for (int i = 0; i < xaxisvals.Count; ++i)
                    {
                        Rectangle currentOne = DrawXLabel(g, xaxisvals[i], xaxisvallabels[i], label_font, sf, true);
                        if (!currentOne.IntersectsWith(lastSuccess))
                        {
                            currentOne = DrawXLabel(g, xaxisvals[i], xaxisvallabels[i], label_font, sf, false);
                            lastSuccess = currentOne;
                        }
                    }

                    if (label != null)
                        if (label != "")
                        {
                            int xpix = XLabelRectangle.Left + XLabelRectangle.Width/2;
                            // Get the Size needed to accommodate the formatted Text.
                            Size preferredSize;
                            using (Graphics g2 = CreateGraphics())
                                preferredSize = g2.MeasureString(label, label_font).ToSize();

                            g.DrawString(label, label_font, Brushes.Black, xpix,
                                         XLabelRectangle.Bottom - 4 - preferredSize.Height, sf);
                        }
                }
            }
            if (xg != null)
            {
                var lastSuccess = new Rectangle(0, 0, 0, 0);
                var xsf = new XStringFormat();
                {
                    xsf.Alignment = XStringAlignment.Center;
                    for (int i = 0; i < xaxisvals.Count; ++i)
                    {
                        var currentOne = XDrawXLabel(xg, xaxisvals[i], xaxisvallabels[i], label_font, xsf, true);
                        if (!currentOne.IntersectsWith(lastSuccess))
                        {
                            currentOne = XDrawXLabel(xg, xaxisvals[i], xaxisvallabels[i], label_font, xsf, false);
                            lastSuccess = currentOne;
                        }
                    }

                    if (label != null)
                        if (label != "")
                        {
                            int xpix = XLabelRectangle.Left + XLabelRectangle.Width/2;
                            // Get the Size needed to accommodate the formatted Text.
                            Size preferredSize;
                            using (Graphics g2 = CreateGraphics())
                                preferredSize = g2.MeasureString(label, label_font).ToSize();

                            xg.DrawString(label, label_font, Brushes.Black, xpix,
                                          XLabelRectangle.Bottom - 4 - preferredSize.Height, xsf);
                        }
                }
            }
        }

        /// <summary>
        /// This function is the same as DrawYLabel but draws to an XGraphics pdf rendering object.
        /// </summary>
        private Rectangle XDrawYLabel(XGraphics g, double ypos, string s, Font f, XStringFormat sf,
                                      double y0, double y1, bool fakedraw)
        {
            int ypix = MaptoYPixel(ypos, y0, y1);

            // Get the Size needed to accommodate the formatted Text.
            Size preferredSize;
            using (Graphics g2 = CreateGraphics())
                preferredSize = g2.MeasureString(s, f).ToSize();

            int xpix = YLabelRectangle.Right - 4 - preferredSize.Width;
            var labelrectangle =
                new Rectangle(xpix, ypix - preferredSize.Height/2, preferredSize.Width, preferredSize.Height);
            if (!fakedraw)
            {
                g.DrawString(s, f, Brushes.Black, xpix + preferredSize.Width/2,
                             ypix - preferredSize.Height/2, sf);
                g.DrawLine(Pens.Black, YLabelRectangle.Right, ypix, YLabelRectangle.Right - 3, ypix);
            }
            return labelrectangle;
        }


        private Rectangle DrawYLabel(Graphics g, double ypos, string s, Font f, StringFormat sf,
                                     double y0, double y1, bool fakedraw)
        {
            int ypix = MaptoYPixel(ypos, y0, y1);

            // Get the Size needed to accommodate the formatted Text.
            Size preferredSize;
            using (Graphics g2 = CreateGraphics())
                preferredSize = g2.MeasureString(s, f).ToSize();

            int xpix = YLabelRectangle.Right - 4 - preferredSize.Width;
            var labelrectangle =
                new Rectangle(xpix, ypix - preferredSize.Height/2, preferredSize.Width, preferredSize.Height);
            if (!fakedraw)
            {
                g.DrawString(s, f, Brushes.Black, xpix + preferredSize.Width/2,
                             ypix - preferredSize.Height/2, sf);
                g.DrawLine(Pens.Black, YLabelRectangle.Right, ypix, YLabelRectangle.Right - 3, ypix);
            }
            return labelrectangle;
        }


        protected void DrawAllYLabels(object graphicsObject, Font label_font,
                                      IList<double> yaxisvals, IList<string> yaxisvallabels, string label,
                                      double y0, double y1)
        {
            var g = graphicsObject as Graphics;
            var xg = graphicsObject as XGraphics;

            if (g != null)
            {
                using (var sf = new StringFormat {Alignment = StringAlignment.Center})
                {
                    var last_success = new Rectangle(0, 0, 0, 0);
                    for (int i = 0; i < yaxisvals.Count; ++i)
                    {
                        Rectangle current_one = DrawYLabel(g, yaxisvals[i], yaxisvallabels[i], label_font, sf, y0, y1,
                                                           true);
                        if (!current_one.IntersectsWith(last_success))
                        {
                            current_one = DrawYLabel(g, yaxisvals[i], yaxisvallabels[i], label_font, sf, y0, y1, false);
                            last_success = current_one;
                        }
                    }

                    // Draw the overall label for the y-axis
                    if (label != null)
                        if (label != "")
                        {
                            sf.FormatFlags = StringFormatFlags.DirectionVertical;

                            // Get the Size needed to accommodate the formatted Text.
                            Size preferredSize;
                            using (Graphics g2 = CreateGraphics())
                                preferredSize = g2.MeasureString(label, label_font).ToSize();

                            int xpix = YLabelRectangle.Left;
                            int ypix = YLabelRectangle.Top + YLabelRectangle.Height/2;
                            g.DrawString(label, label_font, Brushes.Black, xpix, ypix - preferredSize.Height/2, sf);
                        }
                }
            }
            if (xg != null)
            {
                var xsf = new XStringFormat {Alignment = XStringAlignment.Center};
                var lastSuccess = new Rectangle(0, 0, 0, 0);
                for (int i = 0; i < yaxisvals.Count; ++i)
                {
                    Rectangle currentOne = XDrawYLabel(xg, yaxisvals[i], yaxisvallabels[i], label_font, xsf, y0, y1,
                                                        true);
                    if (!currentOne.IntersectsWith(lastSuccess))
                    {
                        currentOne = XDrawYLabel(xg, yaxisvals[i], yaxisvallabels[i], label_font, xsf, y0, y1, false);
                        lastSuccess = currentOne;
                    }
                }

                // Draw the overall label for the y-axis
                if (label != null)
                    if (label != "")
                    {
                        // Get the Size needed to accommodate the formatted Text.
                        Size preferredSize;
                        using (Graphics g2 = CreateGraphics())
                            preferredSize = g2.MeasureString(label, label_font).ToSize();

                        int xpix = YLabelRectangle.Left;
                        int ypix = YLabelRectangle.Top + YLabelRectangle.Height/2;
                        xg.DrawString(label, label_font, Brushes.Black, xpix, ypix - preferredSize.Height/2, xsf);
                    }
            }
        }
    }
}